/**
 * RouteInfoOOC.java
 *
 * @author BJ Premore
 */


package SSF.OS.BGP4;


import java.io.*;
import java.util.*;
import java.lang.ClassNotFoundException;
import com.renesys.raceway.OutOfCore.*;


// ===== class SSF.OS.BGP4.RouteInfoOOC ==================================== //
/**
 * A unit of BGP route information.  It contains all information about a
 * particular route which is used by BGP, including the destination, path
 * attributes, and degree of preference.
 */
public class RouteInfoOOC extends RouteInfo {
  
  // ........................ member data .......................... //

  /** The BGPSession with which this route info is associated. */
  private BGPSession bgp;

  /** A segment of out-of-core memory used to store this route info. */
  private Segment seg;


  // ----- RouteInfoOOC() -------------------------------------------------- //
  /**
   * Constructs new route information with default values.
   *
   * @param b  The BGPSession with which this route info is associated.
   */
  public RouteInfoOOC(BGPSession b) {
    this(b, null, AGG_NONE, null, MIN_DOP, false, false, null);
  }

  // ----- RouteInfoOOC(Route,int,Route,int,boolean, ----------------------- //
  // -----              boolean,boolean,PeerEntry)   ----------------------- //
  /**
   * Constructs route information given all of the relevant data.
   *
   * @param b     The BGPSession with which this route info is associated.
   * @param r     The route to which this information pertains.
   * @param aggs  The aggregation status of this information.
   * @param aggr  An aggregation of this route with others (may be null).
   * @param d     The degree of preference of the route.
   * @param feas  Whether or not the route is feasible.
   * @param perm  Whether or not the route is permissible.
   * @param pe    The peer who advertised this route.
   */
  public RouteInfoOOC(BGPSession b, Route r, int aggs, Route aggr, int d,
                      boolean feas, boolean perm, PeerEntry pe) {

    bgp = b;
    try {
      synchronized (SegmentFactory.lock) {
        seg = Net.heap.malloc(500);
        DataOutput out = seg.getOutput();
        seg.seek();
        out.writeInt(d); // degree of preference
        out.writeInt(bgp.nh2peerind(pe.nh)); // peer index
        out.writeBoolean(feas);  // feasibility
        out.writeBoolean(perm);  // permissibility
        out.writeBoolean(false); // in Loc-RIB
        out.writeBoolean(false); // whether implicit
        r.writeExternal(out);
        //ByteArrayOutputStream bs = new ByteArrayOutputStream();
        //DataOutputStream bout = new DataOutputStream(bs);
        //r.writeExternal(bout);
        //System.out.println("routeinfo size: "+bs.size());
      }
    } catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }
  }

  // ----- RouteInfoOOC(BGPSession,Route,int,boolean,PeerEntry) ------------ //
  /**
   * Constructs new route information with the given attributes.
   *
   * @param b     The BGPSession with which this route info is associated.
   * @param rte   The route held by this entry.
   * @param dop   The degree of preference of the route.
   * @param feas  Whether or not the route is feasible.
   * @param pe    The entry for the peer who advertised this route.
   */
  public RouteInfoOOC(BGPSession b, Route rte, int dop, boolean feas,
                      PeerEntry pe) {
    this(b, rte, AGG_NONE, null, dop, feas, false, pe);
  }

  /** Get the degree of preference. */
  @Override
public int dop() {
    synchronized (SegmentFactory.lock) {
      try {
        DataInput in = seg.getInput();
        seg.seek();
        return in.readInt(); // dop
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
      return -1;
    }
  }

  /** Set the degree of preference. */
  @Override
public void set_dop(int d) {
    synchronized (SegmentFactory.lock) {
      try {
        DataOutput out = seg.getOutput();
        seg.seek();
        out.writeInt(d); // dop
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
    }
  }

  @Override
public int peerind() {
    synchronized (SegmentFactory.lock) {
      try {
        DataInput in = seg.getInput();
        seg.seek();
        in.readInt(); // dop
        return in.readInt(); // peerind
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
      return -1;
    }
  }

  @Override
public Route route() {
    synchronized (SegmentFactory.lock) {
      Route route = null;
      try {
        DataInput in = seg.getInput();
        seg.seek();
        in.readInt(); // dop
        in.readInt(); // peerind
        in.readBoolean(); // feasibility
        in.readBoolean(); // permissibility
        in.readBoolean(); // in Loc-RIB
        in.readBoolean(); // whether implicit
        route = new Route();
        route.readExternal(in);
      } catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
      }
    return route;
    }
  }

  public void set_route(Route r) {
    synchronized (SegmentFactory.lock) {
      try {
        DataInput in = seg.getInput();
        seg.seek();
        int dop = in.readInt(); // dop
        int peerind = in.readInt(); // peerind
        boolean feas = in.readBoolean(); // feasibility
        boolean perm = in.readBoolean(); // permissibility
        boolean inloc = in.readBoolean(); // inlocrib
        boolean imp = in.readBoolean(); // implicit

        DataOutput out = seg.getOutput();
        seg.seek();
        out.writeInt(dop); // dop
        out.writeInt(peerind); // peerind
        out.writeBoolean(feas); // feas
        out.writeBoolean(perm); // perm
        out.writeBoolean(inloc); // inloc
        out.writeBoolean(imp); // implicit
        r.writeExternal(out); // write route
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
    }
  }

  // ----- feasible -------------------------------------------------------- //
  /**
   * Returns whether or not the route is feasible.  A feasible route is one
   * that could be used, according to BGP specifications.  That is, it does not
   * contain any AS loops or have any other "illegal" properties.
   *
   * @return whether or not the route is feasible
   */
  @Override
public boolean feasible() {
    synchronized (SegmentFactory.lock) {
      DataInput in = seg.getInput();
      try {
        seg.seek();
        in.readInt(); // dop
        in.readInt(); // peerind
        return in.readBoolean(); // feas
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
      return false;
    }
  }

  // ----- set_feasible ---------------------------------------------------- //
  /**
   * Sets the feasibility of the route in this entry.
   *
   * @param b  Whether or not the route is feasible.
   */
  @Override
public void set_feasible(boolean b) {
    synchronized (SegmentFactory.lock) {
      try {
        DataInput in = seg.getInput();
        seg.seek();
        int dop = in.readInt(); // dop
        int peerind = in.readInt(); // peerind

        DataOutput out = seg.getOutput();
        seg.seek();
        out.writeInt(dop); // dop
        out.writeInt(peerind); // peerind
        out.writeBoolean(b); // feasible
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
    }
  }

  // ----- permissible ----------------------------------------------------- //
  /**
   * Returns whether or not the route is permissible.  A permissible route is
   * one which was permitted by the input policy rules.  Permissibility is
   * orthogonal to feasibility.
   *
   * @return whether or not the route is permissible
   */
  @Override
public boolean permissible() {
    synchronized (SegmentFactory.lock) {
      try {
        DataInput in = seg.getInput();
        seg.seek();
        in.readInt(); // dop
        in.readInt(); // peerind
        in.readBoolean(); // feasibility
        return in.readBoolean(); // perm
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
      return false;
    }
  }

  // ----- set_permissible ------------------------------------------------- //
  /**
   * Sets the permissibility of the route in this entry.
   *
   * @param b  Whether or not the route is permissible.
   */
  @Override
public void set_permissible(boolean b) {
    synchronized (SegmentFactory.lock) {
      try {
        DataInput in = seg.getInput();
        seg.seek();
        int dop = in.readInt(); // dop
        int peerind = in.readInt(); // peerind
        boolean feas = in.readBoolean(); // feasibility

        DataOutput out = seg.getOutput();
        seg.seek();
        out.writeInt(dop); // dop
        out.writeInt(peerind); // peerind
        out.writeBoolean(feas); // feas
        out.writeBoolean(b); // perm
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
    }
  }

  // ----- inlocrib -------------------------------------------------------- //
  /**
   * Returns whether or not the route is in the Loc-RIB.
   *
   * @return whether or not the route is in the Loc-RIB
   */
  @Override
public boolean inlocrib() {
    synchronized (SegmentFactory.lock) {
      try {
        DataInput in = seg.getInput();
        seg.seek();
        in.readInt(); // dop
        in.readInt(); // peerind
        in.readBoolean(); // feasibility
        in.readBoolean(); // permissibility
        return in.readBoolean(); // inloc
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
      return false;
    }
  }

  // ----- set_inlocrib ---------------------------------------------------- //
  /**
   * Sets whether or not the route is in the Loc-RIB.
   *
   * @param b  Whether or not the route is in the Loc-RIB.
   */
  @Override
public void set_inlocrib(boolean b) {
    synchronized (SegmentFactory.lock) {
      try {
        DataInput in = seg.getInput();
        seg.seek();
        int dop = in.readInt(); // dop
        int peerind = in.readInt(); // peerind
        boolean feas = in.readBoolean(); // feasibility
        boolean perm = in.readBoolean(); // permissibility

        DataOutput out = seg.getOutput();
        seg.seek();
        out.writeInt(dop); // dop 
        out.writeInt(peerind); // peerind
        out.writeBoolean(feas); // feas
        out.writeBoolean(perm); // perm
        out.writeBoolean(b); // inloc
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
    }
  }

  // ----- implicit -------------------------------------------------------- //
  /**
   * Returns whether or not the last advertisement or withdrawal was implicit.
   *
   * @return whether or not the last advertisement or withdrawal was implicit
   */
  @Override
public boolean implicit() {
    synchronized (SegmentFactory.lock) {
      try {
        DataInput in = seg.getInput();
        seg.seek();
        in.readInt(); // dop
        in.readInt(); // peerind
        in.readBoolean(); // feasibility
        in.readBoolean(); // permissibility
        in.readBoolean(); // inlocrib
        return in.readBoolean(); // implicit
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
      return false;
    }
  }

  // ----- set_implicit ---------------------------------------------------- //
  /**
   * Sets whether or not the last advertisement or withdrawal was implicit.
   *
   * @param b  Whether the last advertisement or withdrawal was implicit.
   */
  @Override
public void set_implicit(boolean b) {
    synchronized (SegmentFactory.lock) {
      try {
        DataInput in = seg.getInput();
        seg.seek();
        int dop = in.readInt(); // dop
        int peerind = in.readInt(); // peerind
        boolean feas = in.readBoolean(); // feasibility
        boolean perm = in.readBoolean(); // permissibility
        boolean inloc = in.readBoolean(); // inlocrib

        DataOutput out = seg.getOutput();
        seg.seek();
        out.writeInt(dop); // dop
        out.writeInt(peerind); // peerind
        out.writeBoolean(feas); // feas
        out.writeBoolean(perm); // perm
        out.writeBoolean(inloc); // inloc
        out.writeBoolean(b); // implicit
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
    }
  }

  // ----- compare --------------------------------------------------------- //
  /**
   * Performs a comparison with route information for another route to
   * determine which route is more preferable.
   *
   * @param rte  The route information to compare to.
   * @return 1 if this route is preferred, 0 if they are identically
   *         preferable, and -1 if the given route is preferred.
   */
  @Override
public int compare(RouteInfo ri) {
    if (ri == null) {
      return 1;
    }
    RouteInfoOOC info = (RouteInfoOOC)ri;
    int dop = dop();
    int dop2 = info.dop();
    Route route = route();
    Route route2 = info.route();
    Debug.gaffirm(route.nlri.equals(route2.nlri),
                 "Cannot compare routes with different destinations.");

    if (dop < dop2) {
      return -1;
    } else if (dop > dop2) {
      return 1;
    }

    int peerind = peerind();
    int peerind2 = info.peerind();

    // Their degrees of preference are equal.  If both routes were received
    // from BGP speakers in the same AS, then the first tiebreaker uses the
    // MULTI_EXIT_DISC path attribute.  If not, we skip to the next tiebreaker.
    if (!Global.basic_attribs &&
        bgp.nbs[peerind].as_nh.equals(bgp.nbs[peerind2].as_nh)) {
      // Having a MED is better than not.  (See 9.1.2.1, where it says that the
      // highest value should be assumed when MED is not present.  Since lower
      // is better for MEDs, no MED is the worst possible.)
      if (route.has_med() && !route2.has_med()) {
        return 1;
      } else if (!route.has_med() && route2.has_med()) {
        return -1;
      }
      if (route.has_med() && route2.has_med()) {
        if (route.med() < route2.med()) {
          return 1;
        } else if (route.med() > route2.med()) {
          return -1;
        }
      }
    }

    // Their MULTI_EXIT_DISC values are the same (or both routes were
    // not received from BGP speakers in the same AS), so go to the
    // next tiebreaker, which is based on cost (interior distance).
      // here we're supposed to compare interior distance/cost, but
      // that would seem to imply that forwarding tables could be
      // inconsistent across BGP speakers within this same AS, so
      // we'll forego this comparison until I understand it correctly


    // (This next part (comparing the sources of the routes) is apparently not
    // used for internal tie-breakers (section 9.2.1.1).  Note, however, that
    // it is used during route selection in Phase 2 of the Decision process
    // (section 9.1.2.1).

    // Their costs are the same, go to the next tiebreaker, which is
    // the source of the route (External or Internal BGP peer)
    if (bgp.nbs[peerind].internal() && !bgp.nbs[peerind2].internal()) {
      return -1;
    } else if (!bgp.nbs[peerind].internal() && bgp.nbs[peerind2].internal()) {
      return 1;
    }

    // Their sources are the same, go to next tiebreaker, which is lowest BGP
    // ID of the BGP speakers that advertised them.  (An alternate
    // implementation is to randomize the choice if it gets to this point.)
    if (Global.random_tiebreaking) {
      if (bgp.rng2.nextDouble() < 0.5) {
        return 1;
      } else {
        return -1;
      }
    } else if (Global.fcfc) {
      // We're doing first-come first-chosen, so since there's a tie in
      // preference for these two routes, we take the one that was learned
      // first.  For now, we assume that the route given as an argument to this
      // method was the previously learned route.  (This isn't generally a good
      // assumption to make; this should be fixed by giving every route a new
      // "time learned" field.)
      return -1;
    } else {
      if (bgp.nbs[peerind].bgp_id.val() < bgp.nbs[peerind2].bgp_id.val()) {
        return 1;
      } else if (bgp.nbs[peerind].bgp_id.val() >
                                              bgp.nbs[peerind2].bgp_id.val()) {
        return -1;
      }
    }

    // They're exactly tied all the way around.
    return 0;
  }

  // ----- toBytes --------------------------------------------------------- //
  /**
   * Converts route info into a series of bytes and inserts them into a given
   * byte array.
   *
   * @param bytes   A byte array in which to place the results.
   * @param bindex  The index into the given byte array at which to begin
   *                placing the results.
   * @param usenhi  Whether or not to use NHI addressing.
   * @return the total number of bytes produced by the conversion
   */
  @Override
public int toBytes(byte[] bytes, int bindex, boolean usenhi) {
    synchronized (SegmentFactory.lock) {
      int startindex = bindex;

      DataInput in = seg.getInput();
      int peerind = -1;
      boolean feasible = false, inlocrib = false;
      Route route = null;
      try {
        seg.seek();
        in.readInt(); // dop
        peerind = in.readInt(); // peerind
        feasible = in.readBoolean(); // feasibility
        in.readBoolean(); // skip permissibility
        inlocrib = in.readBoolean(); // inlocrib
        in.readBoolean(); // skip implicit
        route = new Route();
        route.readExternal(in);
      } catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
      }

      // ---- status codes ----
      bytes[bindex++] = (byte)(feasible?1:0);
      bytes[bindex++] = (byte)(inlocrib?1:0);

      // ---- network ----
      bindex += Monitor.ipprefix2bytes(route.nlri, bytes, bindex, usenhi);
      
      // ---- next hop ----
      bytes[bindex++] = (byte)((bgp.nbs[peerind]==bgp.self)?1:0);
      bindex += Monitor.ipprefix2bytes(route.nexthop(), bytes, bindex, usenhi);

      // ---- metric ----
      // nothing

      // ---- local pref ----
      bytes[bindex++] = (byte)(route.has_localpref()?1:0);
      if (route.has_localpref()) {
        bindex = BytesUtil.intToBytes(route.localpref(), bytes, bindex);
      }

      // ---- weight ----
      // nothing

      // ---- AS path ----
      bindex += Monitor.aspath2bytes(route, bytes, bindex, usenhi);

      // ---- internal ----
      bytes[bindex++] = (byte)((bgp.nbs[peerind].internal())?1:0);

      return bindex - startindex;
    }
  }

  // ----- toString() ------------------------------------------------------ //
  /**
   * Returns route information as a string.
   *
   * @return the route information as a string
   */
  @Override
public String toString() {
    return toString(false);
  }

  // ----- toString(boolean) ----------------------------------------------- //
  /**
   * Returns route information as a string.
   *
   * @param usenhi  Whether or not to use NHI addressing.
   * @return the route information as a string
   */
  @Override
public String toString(boolean usenhi) {
    synchronized (SegmentFactory.lock) {
      DataInput in = seg.getInput();
      int peerind = -1;
      boolean feasible = false, inlocrib = false;
      Route route = null;
      try {
        seg.seek();
        in.readInt(); // dop
        peerind = in.readInt(); // peerind
        feasible = in.readBoolean(); // feasibility
        in.readBoolean(); // skip permissibility
        inlocrib = in.readBoolean(); // inlocrib
        in.readBoolean(); // skip implicit
        route = new Route();
        route.readExternal(in);
      } catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
      }

      String str = "";

      // ---- status codes ----
      String feas = "*", best = ">";
      if (!feasible) { feas = " "; }
      if (!inlocrib) { best = " "; }
      str += feas + best + "   ";

      // ---- network ----
      str += StringManip.pad(route.nlri.toString(usenhi), 19, ' ', true);
      
      // ---- next hop ----
      if (bgp.nbs[peerind] == bgp.self) {
        str += "self              ";
      } else {
        str += StringManip.pad(route.nexthop().toString(usenhi), 18, ' ', true);
      }

      // ---- metric ----
      str += "    -";

      // ---- local pref ----
      if (route.has_localpref()) {
        str += StringManip.pad(""+ route.localpref(), 7, ' ', false);
      } else {
        str += "      -";
      }

      // ---- weight ----
      str += "      -";

      // ---- AS path ----
      str += " " + StringManip.pad(route.aspath_toMinString(' ',usenhi),
                                   9, ' ', true);

      // ---- internal ----
      if (bgp.nbs[peerind].internal()) {
        str += " i";
      }

      return str;
    }

  }

  /**
   * Frees segment of allocated out-of-core memory.
   */
  @Override
public void finalize() throws java.lang.Throwable {
    if (seg != null) {
      try {
        seg.free();
      } catch (IOException e) {
        e.printStackTrace();
        System.exit(1);
      }
    }
    super.finalize();
  }


} // end of class RouteInfoOOC
